iT邦幫忙

2023 iThome 鐵人賽

2
自我挑戰組

用ChatGPT詠唱來完成工作與點亮前後端技能樹系列 第 54

【Day54】ChatGPT幫我完成工作:不會python也能用python爬蟲做出IT邦幫忙自動發文神器

  • 分享至 

  • xImage
  •  

這篇基本上就是 【Python】鐵人賽草稿自動排程發文神器 & line notifiy 這一篇的撰寫過程

我先說一下,我實在不會python
倒不是說完全沒有碰過,大學因為專題要做機器學習相關所以摸過一下

但是我自己很不喜歡python的縮排機制
再加上畢業後都在做前端相關的,跟JS之類的語言打滾
python的語法幾乎是退化光了,從安裝到各種語法,幾乎都是ChatGPT提供給我的

這個專案最後有放github上,連裡面的readme都有請ChatGPT幫忙 XD
所以我們來GOGO吧

設定目標

先決定我的程式要有哪些功能

  1. 自動獲取目前帳號的文章列表,可以篩出「草稿」文章有哪些
  2. 能夠取得最早建立的「草稿」文章資料
  3. 能夠將指定的「草稿」文章給正式發文出去

額外功能的話大概是:

  1. [ 額外 ] 程式本身能夠設定時間,每天到這個時間時執行「發表最早建立的草稿」的程序
  2. [ 額外 ] 發文之後或是進行動作之後,可以發送line notifiy 告知有發文成功
  3. [ 額外 ] 做成line bot服務,可以從line中下指令,操控服務的啟用
  4. [ 額外 ] 打包成exe檔,方便運行主機直接執行,不用安裝python與相關套件

先備知識

首先最基礎需要知道以下幾點:

1. 「發文」這個動作其實就是「向伺服器送出request」

所以後續會來解析研究iT邦幫忙做出「發文」這個動作時候的request

2. 取得草稿文章列表,就是要做「爬蟲」這個動作

發送request給主機,接著從收到的html中找出要的資料

3. 發文、取得草稿列表等操作,需要有「該使用者權限」才能進行

這時候就需要取得使用者權限,可能是存在cooike或是session中
發送request時必須攜帶才可以

決定實作語言

以後端語言來說,我比較熟悉的是nodeJS或php

但不太確定nodeJS做爬蟲解析到底容不容易,我本身在這之前沒做過爬蟲
而且終極目標我是希望做成exe檔,方便丟到電腦執行
nodeJS的話就是要靠 npm run

所以,雖然python不是我熟悉的語言,我還是選擇用python來實作
而且能夠順便測試是否能夠靠ChatGPT用不熟悉語言開發
接下來要一步一步地從0到變出程式,記錄了踩坑的過程

另外我是已經開發完程式,再回頭寫這篇
重新問一次ChatGPT,然後重新建起

由於ChatGPT的回答具有隨機性,因此回答的命名、細節做法可能和我目前發布的版本不同
如果懶得看這麼繁瑣的步驟,也可以直接跳最後看結論

開發套件安裝:

Python與相關套件安裝

沒錯,我連python要怎麼裝都忘記了,所以我有問ChatGPT要怎麼裝
https://ithelp.ithome.com.tw/upload/images/20231108/201357504gCmOum0FJ.png

你好!要安裝Python,你可以按照以下步驟進行:

  1. 前往官方網站

    開啟瀏覽器,前往 Python 的官方網站。網址是:https://www.python.org/。

  2. 下載適用於你的作業系統的安裝程式

    在官網首頁,你會看到一個大大的「下載」按鈕。點擊它,系統會自動選擇適用於你作業系統的版本,然後你就可以開始下載。

  3. 執行安裝程式

    下載完成後,找到安裝檔案並執行它。安裝程式會引導你完成安裝過程。

  4. 選擇安裝選項

    在安裝過程中,會有一個選項是「Add Python X.Y to PATH」(X.Y 代表你安裝的 Python 版本)。請確保這個選項被勾選上,這樣在命令列中才能直接執行 Python。

  5. 完成安裝

    完成上述步驟後,等待安裝程式執行完畢。一旦完成,你就成功安裝了 Python。

希望這個步驟對你有所幫助!如果你有任何問題或需要進一步的協助,隨時告訴我。

OK 這一段很單純,就是去官網載,然後裝

如何發送request

由於提到「發文」這個動作其實就是要發出request
所以來問ChatGPT要怎麼用python來發request
https://ithelp.ithome.com.tw/upload/images/20231108/20135750jXphzcO2qr.png

要使用 Python 發出 HTTP 請求,你可以使用 requests 模組。以下是一個簡單的範例,示範如何使用 Python 來發出 GET 請求:

首先,確保你已經安裝了 requests 模組。如果你還沒有安裝,可以執行以下指令:

pip install requests

接著,你可以在 Python 程式中使用以下程式碼來發出 GET 請求:

import requests

url = '<https://api.example.com/endpoint>'  # 將這裡的 URL 替換成你想要訪問的網址

response = requests.get(url)

if response.status_code == 200:
    print('請求成功!')
    print('回應內容:', response.text)
else:
    print(f'請求失敗,錯誤碼:{response.status_code}')

這段程式碼會向指定的 URL 發出一個 GET 請求,然後檢查回應的狀態碼。如果狀態碼是 200,表示請求成功,然後你可以透過 response.text 來取得回應的內容。

如果你需要發出其他類型的請求(例如 POST、PUT、DELETE 等),可以使用 requests.post()requests.put()requests.delete() 等函數。

希望這能幫到你!如果你有任何進一步的問題,歡迎隨時詢問。

好耶,他跟我說要用 requests 這個套件,然後也教我要怎麼安裝
不過我很快就發現一個問題:我使用pip的時候,系統回我沒有這個指令

如何安裝pip

https://ithelp.ithome.com.tw/upload/images/20231108/201357500AFqwihwfe.png

若你是 Python 2.7.9 以及之後的版本,或是 Python 3.4 以及之後的版本,pip 已經預先安裝了。

如果你的 Python 版本符合上述條件,但你確定 pip 還未安裝,可以在命令列中輸入:

pip --version

這會顯示 pip 的版本信息。

然而,如果你使用的是較早版本的 Python(例如 Python 2.7.8 以及之前的版本,或是 Python 3.3 以及之前的版本),你可能需要手動安裝 pip。以下是手動安裝 pip 的步驟:

  1. 下載 get-pip.py

    打開瀏覽器,前往 get-pip.py 並將網頁上的程式碼儲存為 get-pip.py

  2. 執行安裝

    在命令列中,使用以下指令執行 get-pip.py

    python get-pip.py
    

    如果你同時安裝了 Python 2 和 Python 3,你可能需要使用 python3 來執行 get-pip.py

    python3 get-pip.py
    
  3. 檢查安裝

    安裝完成後,你可以使用以下指令來確認 pip 是否成功安裝:

    pip --version
    

希望這能幫到你!如果你遇到了任何問題,隨時向我詢問。

OK,基本上到這邊基礎安裝就完成了,後續有其他套件要安裝就都不是大問題了

最基本的requests 套件裝好了
接下來先來研究到底要發什麼reques才能取得列表

執行方式

yes… 我完全忘記python寫好的東西要怎麼跑起來
https://ithelp.ithome.com.tw/upload/images/20231108/20135750wC9mLC4OyR.png

運行一個寫好的 Python 程式可以通過命令列或集成開發環境(IDE)來進行。以下是兩種方式:

方式一:使用命令列

  1. 開啟命令列

    • 在 Windows 上,可以使用開始菜單中的「命令提示字元」或「PowerShell」。
    • 在 macOS 或 Linux 上,可以使用終端機。
  2. 導航至程式所在的目錄

    使用 cd 命令(change directory)來進入存放你的 Python 程式的目錄。

    例如,如果你的程式位於 C:\\Users\\YourUsername\\Documents(Windows)或是 /Users/YourUsername/Documents(macOS/Linux)下,你可以使用:

    cd Documents
    
  3. 執行程式

    一旦你進入了程式所在的目錄,你可以使用 python 命令來執行 Python 程式。例如:

    python your_program.py
    

    這裡的 your_program.py 是你想要執行的 Python 檔案的名稱。

方式二:使用集成開發環境(IDE)

如果你使用一個 IDE(例如 Visual Studio Code、PyCharm、Jupyter Notebook 等),通常你可以直接在 IDE 中打開並運行 Python 程式。

  1. 打開 IDE

    開啟你選擇的 IDE。

  2. 打開程式

    在 IDE 中打開你想要執行的 Python 程式。

  3. 選擇運行

    大多數 IDE 都會提供一個「運行」或「執行」按鈕,你可以通過點擊這個按鈕來運行你的程式。

無論你選擇哪種方式,都需要確保你已經在你的系統中安裝了 Python,並且已經將 pythonpython3 加入了環境變數中,這樣你才能在命令列中使用 python 命令來執行程式。

希望這能幫到你!如果你遇到了任何問題,請隨時告訴我。

https://ithelp.ithome.com.tw/upload/images/20231108/20135750j3iEzMCWbh.png

看起來在你的系統中,無法找到 python 命令。這可能是由於環境變數未正確設定所致。

你可以嘗試以下方法:

  1. 使用 python3 命令

    在一些系統中,Python 3 的執行命令可能是 python3 而不是 python。你可以試著在命令列中輸入:

    python3 your_program.py
    

    如果這樣可以正常運行,這表示在你的系統中 python3 是正確的 Python 執行命令。

  2. 確認 Python 是否正確安裝

    你可以再次確認一下是否成功安裝了 Python。請在終端機中輸入:

    python --version
    

    或者如果是 Python 3:

    python3 --version
    

    如果顯示了 Python 的版本號,這表示 Python 已經正確安裝了。

  3. 檢查環境變數

    確保你已經將 Python 的安裝路徑加入了系統的環境變數中。這樣系統才能找到 python 命令。

    • 對於 macOS 或 Linux,你可以在終端機中輸入以下命令檢查:

      echo $PATH
      

      確保 Python 的安裝路徑(通常是 /usr/local/bin 或類似的路徑)包含在其中。

    • 對於 Windows,請確保 Python 安裝的路徑已添加到系統的 PATH 環境變數中。

如果你仍然遇到問題,請提供更多詳細的系統和安裝信息,我會盡力幫助你解決問題。

哦~要用python3 不是python
OK確定執行python最基本的知識有了,那麼來研究有關request的結構

研究IT邦幫忙的request

文章列表

首先來到自己的文章列表,可以看到這邊有一些草稿
然後觀察取得這個頁面的request
https://ithelp.ithome.com.tw/upload/images/20231108/20135750gzxF45ZBWz.png

以我自己的頁面為例,發文列表的網址是
https://ithelp.ithome.com.tw/users/20135750/articles

https://ithelp.ithome.com.tw/upload/images/20231108/201357506SGzs1KRXG.png

請求方式是 GET,可以得到一串html的內容
其中有一個關鍵是「20135750」這個部分,可以猜測這個是使用者的id

這裡先不爬蟲獲取,先固定url就好

import requests
url = 'https://ithelp.ithome.com.tw/users/20135750/articles'  # 將這裡的 URL 替換成你想要訪問的網址
response = requests.get(url)
if response.status_code == 200:
    print('請求成功!')
    print('回應內容:', response.text)
else:
    print(f'請求失敗,錯誤碼:{response.status_code}')

執行之後馬上得到,錯誤碼:403

https://ithelp.ithome.com.tw/upload/images/20231108/20135750iom7yLSgCE.png

錯誤碼 403 代表你向該網站發出的請求被伺服器拒絕了,通常是由於權限問題或者伺服器的安全性設定所致。

在這種情況下,你可以考慮以下幾點:

  1. 確認網址的正確性

    確保 url 變數中的網址正確,並且可以在瀏覽器中正常訪問。

  2. 網站需要身份驗證

    有些網站需要身份驗證才能訪問特定頁面。如果是這種情況,你可能需要提供合適的帳號和密碼,或者使用 API 密鑰來訪問該頁面。

  3. 模擬瀏覽器行為

    一些網站可能會檢測到你的請求是來自於爬蟲程序而非瀏覽器,並因此拒絕訪問。你可以嘗試模擬瀏覽器的行為,例如設置 User-Agent 標頭,以使你的請求看起來更像是來自於瀏覽器。

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
    response = requests.get(url, headers=headers)
    

如果你仍然遇到問題,請檢查網站的使用條款以確保你的使用方式合法,並確保你的請求不會違反網站的規則。如果這是一個需要身份驗證或授權的網站,請參考相關的 API 文件或聯繫網站管理員以獲取更多幫助。

選了第三個解法,加上這個headers之後,馬上可以獲取一大堆html
好,我還不會python的function寫法
https://ithelp.ithome.com.tw/upload/images/20231108/20135750i7bNVLhmfm.png

很好,你成功解決了問題!

要將你的程式轉換成一個函數,你可以按照以下步驟進行:

import requests

def get_url_content(url):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        print('請求成功!')
        return response.text
    else:
        print(f'請求失敗,錯誤碼:{response.status_code}')
        return None

url = 'https://ithelp.ithome.com.tw/users/20135750/articles'
content = get_url_content(url)

if content is not None:
    print('回應內容:', content)

在這個範例中,我們將原本的程式轉換成了一個名為 get_url_content 的函數。這個函數接受一個 URL 作為參數,並返回請求的內容(如果請求成功的話)。

你可以將其他的 URL 傳遞給這個函數,以獲得不同網頁的內容。

希望這對你有所幫助!如果你有任何進一步的問題,請隨時告訴我。

抽成function好多了,但是檢視了一下,得到的html沒有包含草稿的文章部分

設定Cookie

草稿只有登入狀態下,又是本人的頁面才會顯示
所以我們從中去找尋cookie,在要求標頭的地方,找到了cookie (反白的的部分)
https://ithelp.ithome.com.tw/upload/images/20231108/20135750oCCrO6bJxr.png
好的,顯然我們的請求也要帶這些東西

繼續來問ChatGPT
https://ithelp.ithome.com.tw/upload/images/20231108/20135750aCV4Trdjta.png

要在請求的 header 中包含 cookie,你可以在 headers 字典中加入 Cookie 標頭。以下是一個範例:

import requests

def get_url_content_with_cookie(url, cookie_string):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Cookie': cookie_string  # 在這裡加入 cookie 字串
    }
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        print('請求成功!')
        return response.text
    else:
        print(f'請求失敗,錯誤碼:{response.status_code}')
        return None

url = '<https://ithelp.ithome.com.tw/users/20135750/articles>'
cookie = 'your_cookie_string_here'  # 將你的 cookie 字串替換在這裡

content = get_url_content_with_cookie(url, cookie)

if content is not None:
    print('回應內容:', content)

在這個範例中,我們將 get_url_content 函數擴展為 get_url_content_with_cookie 函數,並新增了一個名為 cookie_string 的參數。這個參數用於傳遞 cookie 字串。

當你呼叫這個函數時,將你的 cookie 字串傳遞給 cookie_string 參數,它就會被包含在請求的 header 中。

請記得將 your_cookie_string_here 替換為你實際的 cookie 字串。

希望這能對你有所幫助!如果你有任何進一步的問題,請隨時告訴我。

於是把上述的內容依此類推成我們的程式碼
把cookie帶入之後真的成功了,得到了一大堆的html

解析HTML並從中獲取所需資訊

用F12觀察列表中草稿的html
https://ithelp.ithome.com.tw/upload/images/20231108/20135750WomMCLOw9J.png

找到我們想要的html div程式碼
每個文章的區塊為 class_='qa-list profile-list' 的div

所以問ChatGPT要怎麼找到、定位這些區塊
https://ithelp.ithome.com.tw/upload/images/20231108/20135750hYduVGCRWw.png

你可以使用 Python 中的解析庫(如Beautiful Soup)來解析 HTML,然後找出符合特定條件的元素。

首先,你需要安裝 Beautiful Soup:

pip install beautifulsoup4

接下來,你可以使用以下程式碼來找出所有符合特定 class 的 <div> 元素:

from bs4 import BeautifulSoup

# 假設 content 是你從網頁獲得的 HTML 內容
# content = get_url_content_with_cookie(url, cookie)

# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(content, 'html.parser')

# 找出所有 class 為 "qa-list profile-list" 的 <div> 元素
divs = soup.find_all('div', class_='qa-list profile-list')

# 印出所有找到的元素
for div in divs:
    print(div.prettify())  # prettify() 會美化輸出

這個程式碼會使用 Beautiful Soup 解析 HTML,然後找出所有 class 為 "qa-list profile-list" 的 <div> 元素。

如果你想要進一步處理這些元素,你可以在 for 迴圈中對每個 div 進行操作,或者提取出內部的文字或其他元素。

請確保 content 變數包含了你從網頁獲得的 HTML 內容。

希望這可以幫到你!如果你有任何進一步的問題,請隨時告訴我。

經過以上的處理
我們可以拿到divs 這個陣列,陣列裡面每個都大概像是下面這樣

<div class="qa-list profile-list">
   <div class="profile-list__condition">
      <a class="qa-condition ">
      <span class="qa-condition__count">0</span>
      <span class="qa-condition__text">Like</span></a>
      <a class="qa-condition ">
      <span class="qa-condition__count">0</span>
      <span class="qa-condition__text">留言</span>
      </a>
      <a class="qa-condition  ">
      <span class="qa-condition__count">0</span>
      <span class="qa-condition__text">瀏覽</span>
      </a>
   </div>
   <div class="profile-list__content">
      <h3 class="qa-list__title">
         <span class="title-badge title-badge--draft">
         草稿
         </span>
				 <span class="title-badge title-badge--tech">
         技術
         </span>
          <a href="
            https://ithelp.ithome.com.tw/articles/10318302/draft
            " class="qa-list__title-link">
         【Day5】指南:讓ChatGPT成為程式碼的開發/運維好夥伴
         </a>
      </h3>
      <div class="qa-list__tags">
         <a href="/tags/articles/15th鐵人賽" class="tag">15th鐵人賽</a>
         <a href="/tags/articles/chatgpt" class="tag">chatgpt</a>
         <a href="/tags/articles/程式碼開發" class="tag">程式碼開發</a>
         <a href="/tags/articles/程式碼運維" class="tag">程式碼運維</a>
         <a href="/tags/articles/javascript" class="tag">javascript</a>
      </div>
      <div class="qa-list__info">
         <a title="2023-09-14 23:33:13" class="qa-list__info-time">2023-09-14</a> 由
         <a href="https://ithelp.ithome.com.tw/users/20135750/profile" class="qa-list__info-link">一宵三筵
         </a>
         分享
      </div>
   </div>
</div>

發現草稿的class name 是 title-badge--draft
所以我們希望陣列中可以留下有包含<span class="title-badge title-badge--draft"> 的就好

https://ithelp.ithome.com.tw/upload/images/20231108/20135750jW6m3vHxtJ.png

要篩選出 divs 中內部包含 <span class="title-badge title-badge--draft"><div> 元素,你可以使用 Python 的列表推導式(list comprehension)來進行篩選。以下是一個範例:

from bs4 import BeautifulSoup

# 假設 content 是你從網頁獲得的 HTML 內容
# content = get_url_content_with_cookie(url, cookie)

# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(content, 'html.parser')

# 找出所有 class 為 "qa-list profile-list" 的 <div> 元素
divs = soup.find_all('div', class_='qa-list profile-list')

# 利用列表推導式篩選出包含指定 <span> 的 <div> 元素
filtered_divs = [div for div in divs if div.find('span', class_='title-badge title-badge--draft') is not None]

# 印出符合條件的 <div> 元素
for div in filtered_divs:
    print(div.prettify())  # prettify() 會美化輸出

在這個程式碼中,我們使用列表推導式來遍歷 divs 列表,並對每個 <div> 元素檢查是否內部包含指定的 <span> 元素。如果包含,則將這個 <div> 元素添加到 filtered_divs 列表中。

最終,filtered_divs 中會包含所有符合條件的 <div> 元素。

希望這可以幫助你達到你的目標!如果有任何其他問題,請隨時提出。

如此一來,我們的陣列裡面就會只有草稿了
接下來,要從div中,只取出以下這個標題的部分

<a href="https://ithelp.ithome.com.tw/articles/10318302/draft" 
  class="qa-list__title-link">
    【Day5】指南:讓ChatGPT成為程式碼的開發/運維好夥伴
</a>

看起來每篇文章的草稿連結都是 https://ithelp.ithome.com.tw/articles/10318302/draft
這樣的格式,然後文章會有一個ID 10318302

因此,不但要從div中找出該,還要個別分出:

  • href = https://ithelp.ithome.com.tw/articles/10318302/draft
  • text = 【Day5】指南:讓ChatGPT成為程式碼的開發/運維好夥伴

然後再從href 中找出 id

  • id = 10318302

首先,從一大串html中,找 且 class="qa-list__title-link" 的語法我們已經會了
從取div的語法類推可得為

title_link = div.find('a', class_='qa-list__title-link')

接著我們想要知道要怎麼取出元素的內文和href
https://ithelp.ithome.com.tw/upload/images/20231108/20135750B8Aqhyi3wp.png

你可以使用 Beautiful Soup 的 .find() 方法來找到這個 <a> 元素,然後分別獲取 href 屬性和內部的文字。

以下是範例程式碼:

from bs4 import BeautifulSoup

# 假設你已經取得了包含 <a> 的 div 元素
# 例如:div = soup.find('div', class_='qa-list__title')

# 找到 <a> 元素
a_element = div.find('a', class_='qa-list__title-link')

# 取得 href 屬性
href = a_element['href']

# 取得內部文字
text = a_element.text.strip()  # 使用 strip() 移除空白

print(f'href: {href}')
print(f'Text: {text}')

在這個程式碼中,我們首先使用 .find() 方法找到包含 <a> 元素的 div。接著,我們再次使用 .find() 方法找到這個 <a> 元素。然後,我們可以通過索引 ['href'] 取得 href 屬性,並使用 a_element.text 取得內部文字。

希望這對你有所幫助!如果你有任何其他問題,請隨時提出。

ok! 前連面的find他也幫我寫了,驗證了我知道的方式是對的,也了解

href = a_element['href']
text = a_element.text.strip()

這樣的寫法就能從元素中取得他的attr與內容
最後,為了方便我們要另外取出id
https://ithelp.ithome.com.tw/upload/images/20231108/201357503ZvukfSTrA.png

你可以使用 Python 的正規表達式模組 re 來匹配並提取 URL 中的指定部分。以下是一個示例程式碼,用來從給定的 URL 中提取 articles/ 後面的數字部分:

import re

url = "<https://ithelp.ithome.com.tw/articles/10318302/draft>"

# 使用正規表達式匹配 URL 中的數字部分
match = re.search(r'/articles/(\d+)', url)

if match:
    article_id = match.group(1)
    print(f'文章 ID: {article_id}')
else:
    print('未找到文章 ID')

這個程式碼使用了正規表達式 r'/articles/(\\d+)',其中:

  • /articles/ 匹配 URL 中的 /articles/ 字符串。
  • (\\d+) 匹配並捕獲一個或多個數字。

re.search() 函數將在 URL 中搜索並匹配這個正規表達式,如果匹配成功,就可以使用 match.group(1) 來取得捕獲的數字部分,即文章的 ID。

希望這對你有所幫助!如果你有任何其他問題,請隨時提出。

綜合以上,我們可以實作一個找草稿的文章列表,並把資料整理成
Array<{link: string, text: string, id: string}> 的樣子

用嘴寫程式,開始!
https://ithelp.ithome.com.tw/upload/images/20231108/20135750p1O5Rd7GiE.png

你可以創建一個名為 extract_articles_info 的函數,該函數接受 urlcookie 作為參數,然後執行你所需的操作。以下是完整的程式碼:

import requests
from bs4 import BeautifulSoup
import re

def extract_articles_info(url, cookie):
    # 使用 get_url_content_with_cookie 函數獲取 HTML 內容
    content = get_url_content_with_cookie(url, cookie)

    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找出所有 class 為 "qa-list profile-list" 的 <div> 元素
    divs = soup.find_all('div', class_='qa-list profile-list')

    # 篩選出包含 <span class="title-badge title-badge--draft"> 的 <div> 元素
    filtered_divs = [div for div in divs if div.find('span', class_='title-badge title-badge--draft') is not None]

    # 初始化結果列表
    result = []

    # 逐一處理符合條件的 <div> 元素
    for div in filtered_divs:
        # 找到 <a> 元素
        a_element = div.find('a', class_='qa-list__title-link')

        if a_element:
            # 取得 href 屬性
            link = a_element['href'].strip()

            # 取得內部文字
            text = a_element.text.strip()

            # 使用正規表達式從 link 中提取文章 ID
            match = re.search(r'/articles/(\d+)', link)
            if match:
                article_id = match.group(1)
            else:
                article_id = ''

            # 將結果添加到列表中
            result.append({'link': link, 'text': text, 'id': article_id})

    return result

# 測試函數
url = 'https://ithelp.ithome.com.tw/users/20135750/articles'
cookie = 'your_cookie_string_here'  # 將你的 cookie 字串替換在這裡

articles_info = extract_articles_info(url, cookie)
for article in articles_info:
    print(article)

這個 extract_articles_info 函數會根據你的需求,解析 HTML、篩選元素、提取所需的資訊,然後返回一個包含文章連結、文字和 ID 的字典陣列。只需將 urlcookie 傳遞給這個函數,它就會返回結果。

請記得將 your_cookie_string_here 替換為你實際的 cookie 字串。

希望這有幫助!如果有任何問題,請隨時提出。

雖然他說這是完整的程式碼,不過他並沒有幫我把get_url_content_with_cookie 列在裡面
不過沒關係,總之我們整理一下,並且把userID先單獨抽成一個變數,再把他塞到url裡面

為此,我還特地問了
https://ithelp.ithome.com.tw/upload/images/20231108/20135750Ty9wLxnVSo.png

在 Python 中,你可以使用 f 字符串(格式化字符串)來達到類似於 JavaScript 中的模板字串的效果。

在你的例子中,你可以這樣寫:

userId = '20135750'
url = f'<https://ithelp.ithome.com.tw/users/{userId}/articles>'

這樣,url 變數將包含你所期望的 URL。

這種方法稱為 f 字符串,它允許你在字符串中插入變量的值。在字符串前加上 f ,然後在字符串中用 {} 括起來的地方插入變量名。

希望這能幫到你!如果你有任何進一步的問題,請隨時告訴我。

OK!OK! 於是自己整理一下,就變成這樣

import requests
from bs4 import BeautifulSoup
import re

def get_url_content_with_cookie(url, cookie_string):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Cookie': cookie_string  # 在這裡加入 cookie 字串
    }
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        print('請求成功!')
        return response.text
    else:
        print(f'請求失敗,錯誤碼:{response.status_code}')
        return None

def extract_articles_info(userId, cookie):
    url = f'https://ithelp.ithome.com.tw/users/{userId}/articles'
    # 使用 get_url_content_with_cookie 函數獲取 HTML 內容
    content = get_url_content_with_cookie(url, cookie)

    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找出所有 class 為 "qa-list profile-list" 的 <div> 元素
    divs = soup.find_all('div', class_='qa-list profile-list')

    # 篩選出包含 <span class="title-badge title-badge--draft"> 的 <div> 元素
    filtered_divs = [div for div in divs if div.find('span', class_='title-badge title-badge--draft') is not None]
    # 初始化結果列表
    result = []

    # 逐一處理符合條件的 <div> 元素
    for div in filtered_divs:
        # 找到 <a> 元素
        a_element = div.find('a', class_='qa-list__title-link')

        if a_element:
            # 取得 href 屬性
            link = a_element['href'].strip()

            # 取得內部文字
            text = a_element.text.strip()

            # 使用正規表達式從 link 中提取文章 ID
            match = re.search(r'/articles/(\d+)', link)
            if match:
                article_id = match.group(1)
            else:
                article_id = ''

            # 將結果添加到列表中
            result.append({'link': link, 'text': text, 'id': article_id})

    return result

# 測試函數
userId = '20135750'
cookie = 'cookie'  # 將你的 cookie 字串替換在這裡
articles_info = extract_articles_info(url, cookie)
for article in articles_info:
    print(article)

我們的articles_info 陣列就會變成這樣

[
 {'link': 'https://ithelp.ithome.com.tw/articles/10318302/draft', 
  'text': '【Day5】指南:讓ChatGPT成為程式碼的開發/運維好夥伴', 
  'id': '10318302'
 },
 {'link': 'https://ithelp.ithome.com.tw/articles/10317444/draft', 
  'text': '【Day4】量身打造:利用Custom instructions (自訂指令) 來打造工程師的專屬助手',
  'id': '10317444'
 },
 {'link': 'https://ithelp.ithome.com.tw/articles/10316704/draft', 
  'text': '【Day3】示範:使用chatGPT撰寫程式解決算術相關問題',
  'id': '10316704'
 }
]

針對特定草稿發表成正式文章

先來建立一篇測試用的草稿,並按下儲存草稿,觀察裡面的From Data
https://ithelp.ithome.com.tw/upload/images/20231108/20135750GWIjRelU6R.png

經過觀察與測試
儲存草稿的url格式是

https://ithelp.ithome.com.tw/articles/{id}/draft

發表文章的url格式是

https://ithelp.ithome.com.tw/articles/{id}/publish

儲存草稿與發表正式文章的payload都長一樣,都是

{
	_token: string,
	group: 'tech' #技術文章固定為tech
	_method: 'PUT'
	subject: string,
	descripption: string,
	tags[]: string #第一個tag
	tags[]: string # 第二個tag, 依此類推
}

這邊有幾個點要注意:

  1. 有一組token需要攜帶,且傳送的header裡面也需要這組token,否則會被拒絕
  2. 即使事先儲存過草稿,發布文章的時候要再帶一次一樣的內文、標題、標籤的資料過去
    實際發表文章的內容以API的payload為主,草稿的內容會被蓋掉
  3. 至少要有一個標籤,但最多不能超過五個 (tag數需>=1 且 <=5 )
  4. 多個標籤時,解析成object時看起來是多個一樣的key: tags[]
    但實際上的物件並不能有多個一樣的key,所以這邊需要另外處理
    上面的object是瀏覽器幫我們parse的樣子,實際上傳出去的是:
_token=jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD&group=tech&_method=PUT&subject=test%E6%A8%99%E9%A1%8C&description=test%E5%85%A7%E6%96%87&tags%5B%5D=tag1&tags%5B%5D=test2&tags%5B%5D=%E6%A8%99%E7%B1%A43

可以看到是 「tags%5B%5D=tag1&tags%5B%5D=test2
文字經過decode解碼,然後多個tag以字串相接黏在一起

綜合上述三點,我們要發文之前,除了指定該文章的id之外,還要取得草稿頁面中的一些資訊
所以我們逐一的來解決這些問題

1. token:

這個token一定是藏在程式裡的某個地方,或是取得頁面的時候會一起傳輸過來的
查看了單純的網路傳輸,好像沒有一隻api會特地傳這個token
於是我開了F12,在草稿頁面的html中找尋這組token
https://ithelp.ithome.com.tw/upload/images/20231108/20135750qGylWFsetX.png

讓我找到三個地方有寫這個token
會藏在一個隱藏的input中

<input name="_token" type="hidden" value="jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD">

還有一組script中,要發出request前,會在header中加入

{'X-CSRF-TOKEN': 'jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD'}

所以我們就是先獲取草稿頁面html,然後從草稿頁面中拉出<input name="_token">value

突然發現上面都是針對calss,這次要針對name就不會寫了,只好:
https://ithelp.ithome.com.tw/upload/images/20231108/201357509ceqw02P8K.png

你可以使用 BeautifulSoup 來解析 HTML,然後找到這個 <input> 元素,並取得它的 value 屬性。

以下是一個範例程式碼:

from bs4 import BeautifulSoup

# 假設 content 是你從網頁獲得的 HTML 內容
# content = get_url_content_with_cookie(url, cookie)

# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(content, 'html.parser')

# 找到 <input> 元素
input_element = soup.find('input', {'name': '_token', 'type': 'hidden'})

# 取得 value 屬性
if input_element:
    token_value = input_element['value']
    print(f'_token 的 value 為: {token_value}')
else:
    print('未找到 _token 元素')

這個程式碼首先使用 BeautifulSoup 解析 HTML,然後使用 .find() 方法找到 name 屬性為 _tokentype 屬性為 hidden<input> 元素。接著,我們取得這個元素的 value 屬性。

如果找到了符合條件的 <input> 元素,則會取得它的 value 屬性並印出。如果找不到符合條件的元素,則會顯示一條提示訊息。

希望這對你有所幫助!如果你有任何其他問題,請隨時提出。

所以我們就先來定義function

get_draft_content目標是找出指定的文章id裏,所有準備可以拿來正式發文的資料
以下先找到token

    def get_draft_content(id, cookie):
        url = f'https://ithelp.ithome.com.tw/articles/{id}/draft'
        content = get_url_content_with_cookie(url, cookie)
        soup = BeautifulSoup(content, 'html.parser')
        input_element = soup.find('input', {'name': '_token', 'type': 'hidden'})
        if input_element:
            token = input_element['value']

2. 標題、內文、標籤

傳送要把標題、內文、標籤都再帶給api一次
所以一樣就是從html中去找出標題、內文、標籤的tag

  • 標題:
    標題很好找,就是一個 <input name="subject" >的input中的value
    https://ithelp.ithome.com.tw/upload/images/20231108/20135750ioJ4rV506p.png
subject = soup.find('input', {'name': 'subject'})['value']
  • 內文:
    在儲存草稿的時候我就有注意到,這個網站的js有一段在處理內文
    總之他會取出<textarea name="description"> 的內容,轉回markdown文字再儲存傳輸

https://ithelp.ithome.com.tw/upload/images/20231108/20135750mxIvRCZuOd.png

基本上直接獲取的頁面中,原本的text內容就是markdown文字了
所以只要

description = soup.find('textarea', {'name': 'description'}).text
  • 標籤
    https://ithelp.ithome.com.tw/upload/images/20231108/201357502Z7paCVuXe.png
    標籤內部的需要先找到這個select,然後再抽出內部的option的內容
    如果夠熟悉語言實作,就可以自己處理了

但我這邊繼續實驗用嘴寫程式
https://ithelp.ithome.com.tw/upload/images/20231108/20135750h6l27j5AzR.png

你可以使用 Beautiful Soup 解析 HTML,然後找到 <select> 元素,遍歷所有 <option> 元素,並提取其 value 屬性的值。

以下是一個示例程式碼:

from bs4 import BeautifulSoup

# 假設 content 是你的 HTML 字符串
# content = '<select id="tags" name="tags[]" class="form-control select2-hidden-accessible" multiple="" tabindex="-1" aria-hidden="true"> ...'

# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(content, 'html.parser')

# 找到名為 "tags" 的 <select> 元素
select_element = soup.find('select', id='tags')

# 初始化一個空的列表
tags = []

# 遍歷所有 <option> 元素
for option in select_element.find_all('option'):
    value = option['value']
    tags.append(value)

print(tags)

這個程式碼會解析 HTML,找到名為 "tags" 的 <select> 元素,然後遍歷所有的 <option> 元素,提取它們的 value 屬性的值並添加到 tags 列表中。

最終,tags 列表會包含所有的選項值。
希望這可以幫到你!如果你有任何進一步的問題,請隨時告訴我。

有關寫法我還是不太滿意,我不喜歡遍歷之後append進去
https://ithelp.ithome.com.tw/upload/images/20231108/20135750APvPW7jmAc.png

當然可以!你可以使用列表推導式來簡化這段程式碼。這樣可以更簡潔地遍歷 <option> 元素並提取它們的 value 屬性。

以下是優化後的程式碼:

from bs4 import BeautifulSoup

# 假設 content 是你的 HTML 字符串
# content = '<select id="tags" name="tags[]" class="form-control select2-hidden-accessible" multiple="" tabindex="-1" aria-hidden="true"> ...'

# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(content, 'html.parser')

# 找到名為 "tags" 的 <select> 元素
select_element = soup.find('select', id='tags')

# 使用列表推導式來提取所有的 option 的 value 屬性值
tags = [option['value'] for option in select_element.find_all('option')]

print(tags)

這個優化後的程式碼具有相同的功能,但是使用了列表推導式來簡化遍歷過程。這樣可以使程式碼更簡潔和易讀。

希望這能符合你的需求!如果你有任何其他問題,請隨時告訴我。

OK! 綜合以上,我們可以完成我們的get_draft_content

    def get_draft_content(id, cookie):
        url = f'https://ithelp.ithome.com.tw/articles/{id}/draft'
        content = get_url_content_with_cookie(url, cookie)
        soup = BeautifulSoup(content, 'html.parser')
        input_element = soup.find('input', {'name': '_token', 'type': 'hidden'})
        if input_element:
            token = input_element['value']
            # 使用 BeautifulSoup 解析 HTML
    
            subject = soup.find('input', {'name': 'subject'})['value']
            description = soup.find('textarea', {'name': 'description'}).text
            # 找到名為 "tags" 的 <select> 元素
            select_element = soup.find('select', id='tags')
            # 使用列表推導式來提取所有的 option 的 value 屬性值
            tags = [option['value'] for option in select_element.find_all('option')]
            return {
                'token': token,
                'subject': subject,
                'description': description,
                'tags': tags,
            }

發布正式文章

前面我們已經可以拿到發文所需的所有內容了:tokensubjectdescriptiontags
接下來就是創造發送的post function

帶著期待又怕受傷害的心情問看看ChatGPT可不可以幫我實作出來
https://ithelp.ithome.com.tw/upload/images/20231108/20135750AardSZyuQb.png

你可以實作 publish 函數如下:

import requests

def publish(id, cookie):
    # 使用 get_draft_content 函數取得草稿資訊
    draft_info = get_draft_content(id, cookie)

    # 檢查是否成功取得草稿資訊
    if not draft_info:
        print(f'無法取得草稿資訊,請檢查網路連線或登入狀態')
        return

    # 解構草稿資訊
    token = draft_info['token']
    subject = draft_info['subject']
    description = draft_info['description']
    tags = draft_info['tags']

    # 構建 URL
    url = f'https://ithelp.ithome.com.tw/articles/{id}/publish'

    # 構建 headers
    headers = {
        'Cookie': cookie,
        'X-CSRF-TOKEN': token,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
    }

    # 構建傳送資料
    data = {
        '_token': token,
        'group': 'tech',
        '_method': 'PUT',
        'subject': subject,
        'description': description
    }

    # 將 tags[] 加入傳送資料
    data.update({'tags[]': tag for tag in tags})

    # 發送 POST request
    response = requests.post(url, headers=headers, data=data)

    if response.status_code == 200:
        print('發布成功!')
    else:
        print(f'發布失敗,錯誤碼:{response.status_code}')

這個 publish 函數使用了你之前所提供的 get_draft_content 函數,並根據得到的草稿資訊構建了相應的 URL、headers 和傳送資料。最後,它使用 requests.post 方法發送了一個 POST request。

希望這滿足了你的需求!如果有任何進一步的問題,請隨時提出。

幾乎、幾乎要對了
還記得我說的tags的麻煩問題嗎

ChatGPT 提供的

    data.update({'tags[]': tag for tag in tags})

會造成發出去的文只有最後一個標籤,也就是陣列中若有三個標籤,只會留下最後一個

再觀察一下原本網站儲存的時候的資料
https://ithelp.ithome.com.tw/upload/images/20231108/201357507WN20624T1.png

也就是原本的物件如果是這樣

{
_token: 'jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD'
group: 'tech'
_method: 'PUT'
subject: 'test標題'
description: 'test內文'
tags[]: ['tag1', 'test2','標籤3'
}

就要變成這樣

_token=jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD&group=tech&_method=PUT&subject=test%E6%A8%99%E9%A1%8C&description=test%E5%85%A7%E6%96%87&tags%5B%5D=tag1&tags%5B%5D=test2&tags%5B%5D=%E6%A8%99%E7%B1%A43

https://ithelp.ithome.com.tw/upload/images/20231108/2013575038AEemSmpt.png

你可以使用 urllib.parse.urlencode() 函數將你的資料字典轉換成 URL-encoded 格式。以下是一個示例程式碼:

from urllib.parse import urlencode

data = {
    '_token': 'jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD',
    'group': 'tech',
    '_method': 'PUT',
    'subject': 'test標題',
    'description': 'test內文',
    'tags[]': ['tag1', 'test2', '標籤3']
}

encoded_data = urlencode(data, doseq=True)
print(encoded_data)

這個程式碼會將你的資料字典轉換成 URL-encoded 格式,並且使用 doseq=True 參數來處理陣列。

輸出結果將會是:

_token=jtjNFkKoMI3CBynzIXpCO1vTN3UxyqAnhIQGQQXD&group=tech&_method=PUT&subject=test%E6%A8%99%E9%A1%8C&description=test%E5%85%A7%E6%96%87&tags%5B%5D=tag1&tags%5B%5D=test2&tags%5B%5D=%E6%A8%99%E7%B1%A43

這樣的字串就可以直接用於 POST request 中的 body。

希望這對你有所幫助!如果你有任何其他問題,請隨時告訴我。

哦哦經過這樣處理之後,發出的資料正確了
不過還是有遇到 request 405的問題
https://ithelp.ithome.com.tw/upload/images/20231108/2013575062wZGAKpxW.png

如果你在發布時收到 HTTP 錯誤碼 405,這表示該 URL 不接受 POST 請求,可能僅接受 GET 或其他方法。

有幾個可能的原因:

  1. 錯誤的 HTTP 方法: 確保該 URL 預期的 HTTP 方法是 POST。你在 data 中也使用了 _method: 'PUT',如果該 URL 預期的方法不是 PUT,也可能導致錯誤。
  2. 伺服器端的限制: 某些網站可能會限制特定 URL 或端點的訪問方式。這可能需要查看該網站的 API 文件或聯繫相應的支援團隊以獲取正確的發布方法。
  3. 可能需要其他認證: 有些網站可能需要額外的認證或權杖才能進行發布。

如果你確定 URL 和方法正確,並且網站可能需要進一步的身份驗證或特定的操作,請檢查相關的 API 文件或聯繫相關方以獲取更多幫助。

OK我觀察了header裡面
https://ithelp.ithome.com.tw/upload/images/20231108/20135750sd8aeZY5Ew.png

這裡面有一個Content-Type的設定
我就把我們設定改成一致的,在header中加入 Content-Type
最終的 publish 就長這樣

def publish(id, cookie):
    # 使用 get_draft_content 函數取得草稿資訊
    draft_info = get_draft_content(id, cookie)

    # 檢查是否成功取得草稿資訊
    if not draft_info:
        print(f'無法取得草稿資訊,請檢查網路連線或登入狀態')
        return

    # 解構草稿資訊
    token = draft_info['token']
    subject = draft_info['subject']
    description = draft_info['description']
    tags = draft_info['tags']

    # 構建 URL
    url = f'https://ithelp.ithome.com.tw/articles/{id}/publish'

    # 構建 headers
    headers = {
        'Cookie': cookie,
        'X-CSRF-TOKEN': token,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }

    # 構建傳送資料
    data = {
        '_token': token,
        'group': 'tech',
        '_method': 'PUT',
        'subject': subject,
        'description': description,
        'tags[]': tags,
    }
    encoded_data = urlencode(data, doseq=True)
    # 發送 POST request
    response = requests.post(url, headers=headers, data=encoded_data)

    if response.status_code == 200:
        print('發布成功!')
    else:
        print(f'發布失敗,錯誤碼:{response.status_code}')

稍微運用以上function,來測試看看

userId = '你的userId'
cookie = '你的cookie'
articles_info = extract_articles_info(userId, cookie)

if len(articles_info) > 0:
    id = articles_info[0]['id']
    publish(id, cookie)

發文成功!

取得userId

最後,由於我們程式最外側除了需要提供 cookie之外,還要提供 userId
理論上需要提供的東西越少越好,已經有登入cookie了,理論上我們應該可以拿到userId才是
(不然頁面上點擊「我的發文列表」就應該是找不到的了)

所以來觀察首頁
https://ithelp.ithome.com.tw/upload/images/20231108/20135750ksnLrYFay0.png

找到了! <a id="account">
這個裡面的href 會有userId ,而且account會有帳號名稱

其實從頭走到這裡,爬蟲取出資料的方式差不多都會了,就會直接動手寫
(有時候要描述情境也是要打不少字)

不過我們這邊是實驗用,所以繼續用嘴寫程式
https://ithelp.ithome.com.tw/upload/images/20231108/20135750eq5Q11Fvwr.png

你可以使用 Beautiful Soup 解析 HTML,然後選擇 <a> 元素並提取其 href 屬性和 data-account 屬性的值。

以下是 getUser 函數的示例程式碼:

from bs4 import BeautifulSoup

def getUser(content):
    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找到 id 為 "account" 的 <a> 元素
    a_element = soup.find('a', id='account')

    if a_element:
        # 取得 href 屬性值
        href = a_element['href']

        # 取得 data-account 屬性值
        data_account = a_element['data-account']

        # 使用正規表達式取得 id
        import re
        user_id = re.search(r'/users/(\d+)', href).group(1)

        return {'id': user_id, 'name': data_account}
    else:
        return None

# 假設 content 是你的 HTML 字符串
# content = '<a href="/users/20135750" id="account" data-account="lalame888">我的主頁</a>'
user_info = getUser(content)

if user_info:
    print(user_info)
else:
    print('未找到相應的 <a> 元素')

這個 getUser 函數會解析 HTML,然後找到 id 為 "account" 的 <a> 元素,並提取其 hrefdata-account 屬性的值,然後使用正規表達式提取出用戶 ID。

最後,它會返回一個包含用戶 ID 和名稱的字典。

希望這能符合你的需求!如果你有任何進一步的問題,請隨時告訴我。

OK! 我們只要改造一下,把url和吃的參數修正一下,就可以用了

def getUser(cookie):
    url = 'https://ithelp.ithome.com.tw/' 
    content = get_url_content_with_cookie(url, cookie)
    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找到 id 為 "account" 的 <a> 元素
    a_element = soup.find('a', id='account')

    if a_element:
        # 取得 href 屬性值
        href = a_element['href']

        # 取得 data-account 屬性值
        data_account = a_element['data-account']

        # 使用正規表達式取得 id
        import re
        user_id = re.search(r'/users/(\d+)', href).group(1)

        return {'id': user_id, 'name': data_account}
    else:
        return None

最終結果

達成用程式將草稿文章發表出去
輸入:登入 cookie

執行步驟:

  1. getUser(cookie): 用cookie 取得it邦幫忙的首頁內容,去找該帳號的主頁ID與用戶名
  2. extract_articles_info(userId, cookie): 用帳號主頁ID取得草稿列表,取得草稿文章ID
  3. get_draft_content(id, cookie):透過草稿文章ID,取得準備發文的資料
  4. publish(id, cookie): 呼叫get_draft_content 取得資料後,將文章正式發表出去

真正最後成果,還是去看我另一篇文章: 【Python】鐵人賽草稿自動排程發文神器 & line notifiy

這邊有放我的gitHub與串接說明
有再加上一些選單啊、定時執行、串line notify之類的

但其實定時執行這件事情,也可以用電腦的排程去跑,每日定時開啟程式
就不用讓程式一直維持開啟狀態

這次實驗獲得的程式碼放這邊:
基本上有上面那四個function,就可以拿token做一些能夠發文的程式
使用方式可以依人而異,後面我也放了一個「啟動就直接發佈一篇最早的草稿」的簡易程式碼

import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import urlencode

def get_url_content_with_cookie(url, cookie_string):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Cookie': cookie_string  # 在這裡加入 cookie 字串
    }
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        print('請求成功!')
        return response.text
    else:
        print(f'請求失敗,錯誤碼:{response.status_code}')
        return None

def extract_articles_info(userId, cookie):
    url = f'https://ithelp.ithome.com.tw/users/{userId}/articles'
    # 使用 get_url_content_with_cookie 函數獲取 HTML 內容
    content = get_url_content_with_cookie(url, cookie)

    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找出所有 class 為 "qa-list profile-list" 的 <div> 元素
    divs = soup.find_all('div', class_='qa-list profile-list')

    # 篩選出包含 <span class="title-badge title-badge--draft"> 的 <div> 元素
    filtered_divs = [div for div in divs if div.find('span', class_='title-badge title-badge--draft') is not None]
    # 初始化結果列表
    result = []

    # 逐一處理符合條件的 <div> 元素
    for div in filtered_divs:
        # 找到 <a> 元素
        a_element = div.find('a', class_='qa-list__title-link')

        if a_element:
            # 取得 href 屬性
            link = a_element['href'].strip()

            # 取得內部文字
            text = a_element.text.strip()

            # 使用正規表達式從 link 中提取文章 ID
            match = re.search(r'/articles/(\d+)', link)
            if match:
                article_id = match.group(1)
            else:
                article_id = ''

            # 將結果添加到列表中
            result.append({'link': link, 'text': text, 'id': article_id})

    return result

def get_draft_content(id, cookie):
    url = f'https://ithelp.ithome.com.tw/articles/{id}/draft'
    content = get_url_content_with_cookie(url, cookie)
    soup = BeautifulSoup(content, 'html.parser')
    input_element = soup.find('input', {'name': '_token', 'type': 'hidden'})
    if input_element:
        token = input_element['value']
        # 使用 BeautifulSoup 解析 HTML

        subject = soup.find('input', {'name': 'subject'})['value']
        description = soup.find('textarea', {'name': 'description'}).text
        # 找到名為 "tags" 的 <select> 元素
        select_element = soup.find('select', id='tags')
        # 使用列表推導式來提取所有的 option 的 value 屬性值
        tags = [option['value'] for option in select_element.find_all('option')]
        return {
            'token': token,
            'subject': subject,
            'description': description,
            'tags': tags,
        }

def publish(id, cookie):
    # 使用 get_draft_content 函數取得草稿資訊
    draft_info = get_draft_content(id, cookie)

    # 檢查是否成功取得草稿資訊
    if not draft_info:
        print(f'無法取得草稿資訊,請檢查網路連線或登入狀態')
        return

    # 解構草稿資訊
    token = draft_info['token']
    subject = draft_info['subject']
    description = draft_info['description']
    tags = draft_info['tags']

    # 構建 URL
    url = f'https://ithelp.ithome.com.tw/articles/{id}/publish'

    # 構建 headers
    headers = {
        'Cookie': cookie,
        'X-CSRF-TOKEN': token,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }

    # 構建傳送資料
    data = {
        '_token': token,
        'group': 'tech',
        '_method': 'PUT',
        'subject': subject,
        'description': description,
        'tags[]': tags,
    }
    encoded_data = urlencode(data, doseq=True)
    # 發送 POST request
    response = requests.post(url, headers=headers, data=encoded_data)

    if response.status_code == 200:
        print('發布成功!')
    else:
        print(f'發布失敗,錯誤碼:{response.status_code}')
def getUser(cookie):
    url = 'https://ithelp.ithome.com.tw/' 
    content = get_url_content_with_cookie(url, cookie)
    # 使用 BeautifulSoup 解析 HTML
    soup = BeautifulSoup(content, 'html.parser')

    # 找到 id 為 "account" 的 <a> 元素
    a_element = soup.find('a', id='account')

    if a_element:
        # 取得 href 屬性值
        href = a_element['href']

        # 取得 data-account 屬性值
        data_account = a_element['data-account']

        # 使用正規表達式取得 id
        import re
        user_id = re.search(r'/users/(\d+)', href).group(1)

        return {'id': user_id, 'name': data_account}
    else:
        return None

開啟就直接發一篇最早建立的草稿範例:

user = getUser(cookie)
if user:
    userId = user['id']
    articles_info = extract_articles_info(userId, cookie)
    if len(articles_info) > 0:
        index = len(articles_info) - 1
        id = articles_info[index]['id']
        publish(id, cookie)

結語

寫了一篇落落長的紀錄,本次和ChatGPT的完整對話在這邊:
https://chat.openai.com/share/687b488b-2e97-4bcb-bbb5-0e0bd8e31459

親自實驗不會ChatGPT和爬蟲到底能不能寫出python爬蟲相關程式
答案是可以的,而且花兩天就完成

不用從頭學習python,而是用我本來就會的程式底子
瘋狂詢問ChatGPT如果在python的話要怎麼寫

最初實際開發的時候,我幾乎什麼語法都要問一下
len()也不會、input也不會、else if也不會
總之當我遇到的問題,就是問,再從中去學習、吸取,再最後變成自己的知識

整個點亮前後端技能樹系列想傳達的其實就是:
ChatGPT的程式能力也許比我強,但ChatGPT並不會讓我被取代掉
好好利用與成為使用、操控工具的人,我想是不會餓肚子的

這一篇我很早就寫好放著了,一直在等我前面想講主題講完
最後剩下一篇是完結的結語,無數次地謝謝訂閱、收藏這系列文章的各位

我是不會寫python的前端工程師一宵三筵,我們明天見!


上一篇
【Day53】ChatGPT幫我完成工作:寫Jest單元測試!NestJS & Knex & Jest!
下一篇
【Day55】用ChatGPT詠唱來完成工作與點亮前後端技能樹:完賽感言&結語
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言